#include "task.h"
#include "fifo.h"
#include "common.h"
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/kfifo.h>
#include <linux/delay.h>
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/timex.h>
#include <linux/rtc.h>


int tock_task(void *data)
{
	const int SLEEP_SECS = 5;
	int i = 0;
	int count = *(const int *)data;
	PR_INFO("tock task, total count: %d", count);

	for (i = 0; i < count; ++i) {
		// schedule_timeout_interruptible(SLEEP_SECS * HZ);
		// schedule_timeout_uninterruptible(SLEEP_SECS * HZ);
		msleep_interruptible(SLEEP_SECS * 1000);
		PR_INFO("tock task, have slept %d sec(s)", (i + 1) * SLEEP_SECS);
		if (kthread_should_stop()) {
			PR_INFO("tock task, should stop");
			break;
		}
	}

	PR_INFO("tock task, exit");
	return 0;
}

#define SLEEP_MSECS 200
int print_kfifo_task(void *data)
{
	struct sync_counter_frame frame;
	struct kfifo *fifo = (struct kfifo *)data;
	int ret = 0;
	PR_INFO("print kfifo task, start");
	while(1) {
		ret = kfifo_out(fifo, &frame, sizeof(struct sync_counter_frame));
		if (ret == 0) {
			if (kthread_should_stop()) {
				PR_INFO("print kfifo task, should stop");
				break;
			}
			msleep_interruptible(SLEEP_MSECS);
		} else {
			PR_INFO("kfifo read, count: %llu, ns: %llu,", frame.count, frame.time_ns);
		}
	}
	
	PR_INFO("print kfifo task, exit");
	return 0;
}

struct rtc_time get_rtc_time(void)
{
	ktime_t ktime = ktime_get_real();
	struct rtc_time tm = rtc_ktime_to_tm(ktime + 8LL * 60 * 60 * 1000 * 1000 * 1000);
	PR_INFO("get rtc time, YEAR %d, MONTH %d, DAY: %d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
	return tm;
}



struct file *open_record_file(const char *prefix, uint32_t serial, struct rtc_time tm)
{
	struct file *f = NULL;
	char name_buffer[128];
	int folder_id = (tm.tm_year % 100) * 100 + tm.tm_mon + 1; 

	sprintf(name_buffer, "%s/%04d%02d-%02d%02d%02d_serial_%d",
		prefix, 
		folder_id,
		tm.tm_mday,
		tm.tm_hour,
		tm.tm_min,
		tm.tm_sec,
		serial
	);

	f = filp_open(name_buffer, O_CREAT | O_RDWR, 0644);
	if (IS_ERR(f)) {
		PR_ERR("open record file %s failed, errno %ld", name_buffer, PTR_ERR(f));
		return f;
	}

	return f;
}


int log_kfifo_to_file_task(void *data)
{
	// loff_t pos = 0;
	ssize_t ret = 0;
	uint32_t serial = 0;
	struct rtc_time rtc;

	// file pointer to recorded file
	struct file *filp = NULL;

	// frame buf for sync_frame
	struct sync_counter_frame frame;

	struct log_to_file_config *cfg = (struct log_to_file_config *)(data);
	const char *base_path = cfg->base_path;
	const uint32_t sleep_msecs = cfg->sleep_msecs;
	struct kfifo *fifo = cfg->fifo;


	PR_INFO("log to file task, start");

	// main loop
	while (1) {
		ret = kfifo_out(fifo, &frame, sizeof(struct sync_counter_frame));
		if (ret == 0) {
			if (kthread_should_stop()) {
				PR_INFO("log_kfifo2file task, should stop");
				break;
			}
			msleep_interruptible(sleep_msecs);
		} else {
			PR_DEBUG("read frame, time_ns: %llu, counter: %llu", frame.time_ns, frame.count);
			if (frame.count == TRIGGER_COUNT) {
				serial = frame.time_ns;
				// a new capture, make a new file
				if (filp != NULL) {
					filp_close(filp, current->files);
					filp = NULL;
				}

				PR_INFO("new capture, serial: %u", serial);

				rtc = get_rtc_time();

				filp = open_record_file(base_path, serial, rtc);
				if (IS_ERR(filp)) {
					PR_ERR("open record file failed, errno %ld", PTR_ERR(filp));
					filp = NULL;
					ret = PTR_ERR(filp);
					break;
				}
			} else {
				PR_DEBUG("write frame to record file");
				if (filp == NULL) {
					PR_INFO("flip not init, frame count order error");
					ret = -EINVAL;
					break;
				}
				ret = kernel_write(filp, &frame, sizeof(struct sync_counter_frame), &filp->f_pos);
				if (ret != sizeof(struct sync_counter_frame)) {
					PR_ERR("write frame failed, capture serial %d", serial);
					ret = -EBADTYPE;
					break;
				}
			}
		}
	}

	if (filp != NULL) {
		filp_close(filp, NULL);
		filp = NULL;
	}

	PR_INFO("log_kfifo2file, exit");
	return ret;
}


